package com.easy.mongodb.core.conditions;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.easy.mongodb.common.constants.BaseMongoConstants;
import com.easy.mongodb.common.enums.FieldFill;
import com.easy.mongodb.common.utils.ExceptionUtils;
import com.easy.mongodb.common.utils.StringUtils;
import com.easy.mongodb.core.biz.MongoParam;
import com.easy.mongodb.core.biz.PageInfo;
import com.easy.mongodb.core.biz.TableInfo;
import com.easy.mongodb.core.cache.BaseCache;
import com.easy.mongodb.core.conditions.interfaces.BaseMongoMapper;
import com.easy.mongodb.core.conditions.query.LbqWrapper;
import com.easy.mongodb.core.toolkit.DocumentKit;
import com.easy.mongodb.core.toolkit.PageHelper;
import com.easy.mongodb.core.toolkit.TableInfoHelper;
import com.easy.mongodb.core.toolkit.Wraps;
import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.IndexModel;
import com.mongodb.client.model.InsertManyOptions;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.InsertManyResult;
import com.mongodb.client.result.InsertOneResult;
import com.mongodb.client.result.UpdateResult;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

import static com.easy.mongodb.common.constants.BaseMongoConstants.DEFAULT_MONGO_ID_NAME;

/**
 * @ProductName: easy-mongodb
 * @Package: com.easy.mongodb.core.conditions
 * @Description: 所有支持方法接口实现类
 * @Author: vapeshop
 * @Date: 2022/6/24 10:48
 * @UpdateUser: vapeshop
 * @UpdateDate: 2022/6/24 10:48
 * @UpdateRemark: The modified content
 * @Version: 1.0
 * <p>
 * Copyright © 2022 vapeshop Technologies Inc. All Rights Reserved
 **/
@Slf4j
public class BaseMongoMapperImpl<T> implements BaseMongoMapper<T> {
    /**
     * MongoClient client
     */
    @Getter
    @Setter
    private MongoClient client;

    /**
     * String database
     */
    @Setter
    private MongoDatabase defaultDb;
    /**
     * T 对应的类
     */
    @Setter
    private Class<T> entityClass;
    private String defaultCollection;


    private static Bson or(List<Bson> q) {
        return q.size() == 0 ? new BsonDocument() : Filters.or((Iterable) q);
    }

    private static Bson or(Bson q) {
        return Filters.or(q);
    }

    private static Bson nor(List<Bson> query) {
        return query.size() == 0 ? new BsonDocument() : Filters.nor((Iterable) query);
    }

    public void init(MongoClient client, Class<T> entityClass) {
        this.client = client;
        this.entityClass = entityClass;
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        this.defaultDb = client.getDatabase(tableInfo.getDatabase());
        this.defaultCollection = tableInfo.getCollectionName();
    }

    private <T> T getVal(String val, Class<T> type) {
        // 把val转换成type类型返回
        T value = null;
        try {
            Constructor<T> constructor = type.getConstructor(String.class);
            constructor.setAccessible(true);
            value = constructor.newInstance(val);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return value;
    }

    /**
     * 获取id实际字段名称
     *
     * @return id实际字段名称
     */
    private String getRealIdFieldName() {
        return TableInfoHelper.getTableInfo(this.entityClass).getKeyProperty();
    }

    /**
     * 设置id值
     *
     * @param entity 实体
     * @param id     主键
     */
    private void setId(Object entity, BsonValue id) {
        try {
            if (StringUtils.isBlank(TableInfoHelper.getTableInfo(entity.getClass()).getKeyProperty())) {
                return;
            }
            Method invokeMethod = BaseCache.setterMethod(entity.getClass(), getRealIdFieldName());
            // 将mongo返回的String类型id还原为字段实际的id类型,比如Long,否则反射会报错
            Class<?> idClass = TableInfoHelper.getTableInfo(entity.getClass()).getIdClass();
            String idString = "";
            switch (id.getBsonType()) {
                case INT32:
                    idString = String.valueOf(id.asNumber().intValue());
                    break;
                case INT64:
                    idString = String.valueOf(id.asNumber().longValue());
                    break;
                case OBJECT_ID:
                    idString = id.asObjectId().getValue().toString();
                    break;
                case STRING:
                    idString = id.asString().getValue();
                    break;
                default:
                    break;
            }
            Object val = getVal(idString, idClass);
            invokeMethod.invoke(entity, val);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取实体对象的id值
     *
     * @param entity 实体对象
     * @return id值
     */
    private String getIdValue(T entity) {
        try {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
            Field keyField = Optional.ofNullable(tableInfo.getKeyField())
                    .orElseThrow(() -> ExceptionUtils.build("the entity id field not found"));
            Object value = keyField.get(entity);
            return Optional.ofNullable(value)
                    .map(Object::toString)
                    .orElseThrow(() -> ExceptionUtils.build("the entity id must not be null"));
        } catch (IllegalAccessException e) {
            throw ExceptionUtils.build("get id value exception", e);
        }
    }


    public MongoCollection<Document> getCollection() {
        return this.defaultDb.getCollection(TableInfoHelper.getTableInfo(this.entityClass).getCollectionName());
    }

    @Override
    public Long insert(T entity) {
        InsertOneResult result = getCollection().insertOne(DocumentKit.toDocument(entity, FieldFill.INSERT));
        setId(entity, result.getInsertedId());
        return result.getInsertedId() != null ? 1L : 0L;

    }

    @Override
    public Long insertBatch(Collection<T> entityList) {
        InsertManyResult result = getCollection().insertMany(entityList.stream().map(e -> DocumentKit.toDocument(e, FieldFill.INSERT)).collect(Collectors.toList()));
        return Long.valueOf(result.getInsertedIds().entrySet().size());
    }

    @Override
    public Long deleteById(Serializable id) {
        DeleteResult deleteResult = getCollection().deleteMany(new BasicDBObject(DEFAULT_MONGO_ID_NAME, new ObjectId(id.toString())));
        return deleteResult.getDeletedCount();
    }

    @Override
    public Long deleteById(T entity) {
        String idVal = getIdValue(entity);
        Assert.isFalse(StrUtil.isBlank(idVal), "ID can't be NULL!");
        DeleteResult deleteResult = getCollection().deleteMany(new BasicDBObject(DEFAULT_MONGO_ID_NAME, new ObjectId(idVal)));
        return deleteResult.getDeletedCount();
    }

    @Override
    public Long deleteByMap(Map<String, Object> columnMap) {
        DeleteResult deleteResult = getCollection().deleteMany(DocumentKit.toDocument(columnMap, FieldFill.DEFAULT));
        return deleteResult.getDeletedCount();
    }

    @Override
    public Long delete(Wrapper<T> queryWrapper) {
        DeleteResult deleteResult = getCollection().deleteMany(queryWrapper.getConditions());
        return deleteResult.getDeletedCount();
    }

    @Override
    public Long deleteBatchIds(Collection<?> idList) {
        DeleteResult deleteResult = getCollection().deleteMany(Filters.in(DEFAULT_MONGO_ID_NAME, idList.stream().map(value -> {
            return new ObjectId(String.valueOf(value));
        }).collect(Collectors.toList())));
        return deleteResult.getDeletedCount();
    }

    @Override
    public Long updateById(T entity) {
        String idVal = getIdValue(entity);
        Assert.isFalse(StrUtil.isBlank(idVal), "ID can't be NULL!");
        UpdateResult updateResult = getCollection().updateOne(new BasicDBObject(DEFAULT_MONGO_ID_NAME, new ObjectId(idVal)), DocumentKit.toDocumentForUpdate(entity));
        return updateResult.getModifiedCount();
    }

    @Override
    public Long update(T entity, Wrapper<T> updateWrapper) {
        if (Objects.isNull(entity)) {
            return 0L;
        }
        UpdateResult updateResult = getCollection().updateMany(updateWrapper.getConditions(), DocumentKit.toDocumentForUpdate(entity));
        return updateResult.getModifiedCount();
    }

    @Override
    public T selectById(Serializable id) {
        return selectById(id, true);
    }

    @Override
    public T selectById(Serializable id, boolean isCascade) {
        if (isCascade) {
            Wrapper wrapper = Wraps.lbQ();
            wrapper.getQuery().add(Filters.eq(DEFAULT_MONGO_ID_NAME, new ObjectId(id.toString())));
            return (T) selectList(wrapper).get(0);
        } else {
            return find(this.defaultCollection, Filters.eq(DEFAULT_MONGO_ID_NAME, new ObjectId(id.toString())), new Document(), new Document(), 0, 0, this.entityClass).get(0);
        }

    }

    @Override
    public List<T> selectBatchIds(Collection<? extends Serializable> idList) {
        return find(this.defaultCollection, Filters.in(DEFAULT_MONGO_ID_NAME, idList), new Document(), new Document(), 0, 0, this.entityClass);
    }

    @Override
    public List<T> selectByMap(Map<String, Object> columnMap) {
        return find(this.defaultCollection, Filters.and(DocumentKit.toDocument(columnMap, FieldFill.DEFAULT)), new Document(), new Document(), 0, 0, this.entityClass);
    }

    @Override
    public Long selectCount(Wrapper<T> queryWrapper) {
        return count(this.defaultCollection, queryWrapper.getConditions());
    }

    @Override
    public List<T> selectList(Wrapper<T> queryWrapper) {
        MongoParam mongoParam = WrapperHelper.buildMongoParamBuilder(queryWrapper, this.entityClass);

        if (mongoParam.isEnablePipeline()) {
            return aggregate(this.defaultCollection, mongoParam.getParamList(), false, this.entityClass);
        } else {
            return find(this.defaultCollection, queryWrapper.getConditions(), queryWrapper.getSort(), queryWrapper.getProjection(), queryWrapper.getLimit(), queryWrapper.getSkip(), this.entityClass);
        }
    }

    @Override
    public List<T> selectList(T entity) {
        Document document = DocumentKit.toDocument(entity, FieldFill.DEFAULT);
        LbqWrapper wrapper = Wraps.<T>lbQ((Class<T>) entity.getClass());
        wrapper.getQuery().add(Filters.and(document.toBsonDocument()));
        return this.selectList(wrapper);
    }


    @Override
    public PageInfo<T> page(Wrapper<T> wrapper, Integer pageNum, Integer pageSize) {
// 兼容分页参数
        pageNum = pageNum == null || pageNum <= BaseMongoConstants.ZERO ? BaseMongoConstants.PAGE_NUM : pageNum;
        pageSize = pageSize == null || pageSize <= BaseMongoConstants.ZERO ? BaseMongoConstants.PAGE_SIZE : pageSize;

        List dataList = find(this.defaultCollection, wrapper.getConditions(), wrapper.getSort(), wrapper.getProjection(), pageSize, (pageNum - 1) * pageSize, this.entityClass);
        long count = selectCount(wrapper);
        return PageHelper.getPageInfo(dataList, count, pageNum, pageSize);
    }

    @Override
    public void drop() {
        getCollection(this.defaultCollection).drop();
    }


    public MongoCollection<Document> getCollection(String collectionName) {
        return this.defaultDb.getCollection(collectionName);
    }

    public void insert(String collectionName, List<Document> docs, InsertManyOptions ops) {
        getCollection(collectionName).insertMany(docs, ops);
    }

    @Override
    public void insert(String collectionName, Document doc) {
        getCollection(collectionName).insertOne(doc);
    }

    @Override
    public MongoDatabase getMongoDatabase() {
        return this.defaultDb;
    }


    public <T> List<T> aggregate(String collectionName, List<Bson> query, boolean allowDiskUse, final Class<T> clazz) {
        final List list = new ArrayList();
        getCollection(collectionName).aggregate(query).allowDiskUse(allowDiskUse).forEach(document -> {
            list.add(DocumentKit.toBean(document, clazz));
        });
        return list;
    }

    public long count(String collectionName, Bson query) {
        return getCollection(collectionName).countDocuments(query);
    }

    public long count(String collectionName) {
        return getCollection(collectionName).countDocuments();
    }


    public <T> List<T> find(String collectionName, Bson query, Bson sort, Bson projection, int limit, int skip,
                            final Class<T> clazz) {
        final List list = new ArrayList();
        getCollection(collectionName).find(query).projection(projection).sort(sort).limit(limit).skip(skip).forEach(document -> {
            list.add(DocumentKit.toBean(document, clazz));
        });
        return list;

    }


    public long update(String collectionName, Bson queue, Bson data) {
        UpdateResult updateResult = getCollection(collectionName).updateMany(queue, data);
        return updateResult.getModifiedCount();
    }

    public long updateOne(String collectionName, Bson queue, Bson data) {
        UpdateResult updateResult = getCollection(collectionName).updateOne(queue, data);
        return updateResult.getModifiedCount();
    }

    public long replaceOne(String collectionName, Bson queue, Document document) {
        UpdateResult updateResult = getCollection(collectionName).replaceOne(queue, document);
        return updateResult.getModifiedCount();
    }

    public long delete(String collectionName, Bson queue) {
        DeleteResult deleteResult = getCollection(collectionName).deleteMany(queue);
        return deleteResult.getDeletedCount();
    }

    public long deleteOne(String collectionName, Bson queue) {
        DeleteResult deleteResult = getCollection(collectionName).deleteOne(queue);
        return deleteResult.getDeletedCount();
    }

    public String setIndex(String collectionName, Bson bson) {
        return getCollection(collectionName).createIndex(bson);
    }

    public List<String> setIndex(String collectionName, List<IndexModel> list) {
        return getCollection(collectionName).createIndexes(list);
    }

    public List<String> getIndex(String collectionName) {

        final List list = new ArrayList();
        getCollection(collectionName).listIndexes().forEach(document -> list.add(document.toJson()));
        return list;
    }

    public void deleteIndex(String collectionName, Bson bson) {

        getCollection(collectionName).dropIndex(bson);

    }

    public void deleteIndex(String collectionName) {
        getCollection(collectionName).dropIndexes();
    }

}
